package com.application.fabric;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import io.grpc.ManagedChannel;
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
import net.bytebuddy.agent.VirtualMachine;
import org.hyperledger.fabric.client.*;
import org.hyperledger.fabric.client.identity.*;
import org.hyperledger.fabric.protos.gateway.ErrorDetail;
import org.omg.CosNaming.NamingContextPackage.NotFound;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

public class Main {


    public Main(final Gateway gateway) {
        // Get a network instance representing the channel where the smart contract is
        // deployed.
        Network network = gateway.getNetwork(CHANNEL_NAME);
        System.out.println("--------------------");
        System.out.println(gateway);
        System.out.println("--------------------");

        // Get the smart contract from the network.
        contract = network.getContract(CHAINCODE_NAME);
        System.out.println("--------------------");
        System.out.println(CHAINCODE_NAME);
        System.out.println("--------------------");
    }

    public static void main(final String[] args) throws Exception{
        ManagedChannel channel = newGrpcConnection();
        Gateway.Builder builder = Gateway.newInstance().identity(newIdentity()).signer(newSigner()).connection(channel)
                .evaluateOptions(options -> options.withDeadlineAfter(5, TimeUnit.SECONDS))
                .endorseOptions(options -> options.withDeadlineAfter(15, TimeUnit.SECONDS))
                .submitOptions(options -> options.withDeadlineAfter(5, TimeUnit.SECONDS))
                .commitStatusOptions(options -> options.withDeadlineAfter(1, TimeUnit.MINUTES));

        try (Gateway gateway = builder.connect()){
             new Main(gateway).run();
        }finally {
            channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
        }
    }
    private static final String MSP_ID = System.getenv().getOrDefault("MSP_ID", "Org1MSP");
    private static final String CHANNEL_NAME = System.getenv().getOrDefault("CHANNEL_NAME", "mychannel");
    private static final String CHAINCODE_NAME = System.getenv().getOrDefault("CHAINCODE_NAME", "basic");

  /*  * 初始化路径
    * */


    // Path to crypto materials.
    private static final String pathmain="/home/hatelys/fabric2.2/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com";
    private static final Path CRYPTO_PATH = Paths.get(pathmain);
    // Path to user certificate.
    private static final Path CERT_PATH = CRYPTO_PATH.resolve(Paths.get(pathmain+"/msp/signcerts/cert.pem"));
    // Path to user private key directory.
    private static final Path KEY_DIR_PATH = CRYPTO_PATH.resolve(Paths.get(pathmain+"/msp/keystore"));
    // Path to peer tls certificate.
    private static final Path TLS_CERT_PATH = CRYPTO_PATH.resolve(Paths.get(pathmain+"/peers/peer0.org1.example.com/tls/ca.crt"));
/*
网关节点
    * */

    private static final String PEER_ENDPOINT = "localhost:7051";
    private static final String OVERRIDE_AUTH = "peer0.org1.example.com";
    private Contract contract;
//这个地方使用当前时间戳加上世纪时间作随机数可以在后续代码中进行改进
//    *

    private final String assetId = "asset" + Instant.now().toEpochMilli();
    private final Gson gson = new GsonBuilder().setPrettyPrinting().create();


//静态化函数
//    *

    private static ManagedChannel newGrpcConnection() throws IOException, CertificateException{
         BufferedReader tlsPath =Files.newBufferedReader(TLS_CERT_PATH);
        X509Certificate tlsCert = Identities.readX509Certificate(tlsPath);

//端口生成网络频道

        return NettyChannelBuilder.forTarget(PEER_ENDPOINT)
                .sslContext(GrpcSslContexts.forClient().trustManager(tlsCert).build()).overrideAuthority(OVERRIDE_AUTH)
                .build();

    }

    private static Identity newIdentity() throws IOException, CertificateException{
        BufferedReader certReader = Files.newBufferedReader(CERT_PATH);
        X509Certificate certificate = Identities.readX509Certificate(certReader);

        return new X509Identity(MSP_ID, certificate);
    }

    private static Signer newSigner() throws IOException, InvalidKeyException, NotFound {
        BufferedReader keReader = Files.newBufferedReader(getPrivateKeyPath());
        PrivateKey privateKey = Identities.readPrivateKey(keReader);

        return Signers.newPrivateKeySigner(privateKey);
    }


//私钥获取
//    *

    private static Path getPrivateKeyPath() throws IOException, NotFound {

            try (  Stream<Path> keyFiles = Files.list(KEY_DIR_PATH)){
//注意这边可能出现问题
//                *

                return keyFiles.findFirst().orElseThrow(() -> new NotFound());
            }

    }


//这是监听智能合约的部分  --销毁和实例化
//    *



    public void run() throws GatewayException, CommitException{
        // Initialize a set of asset data on the ledger using the chaincode 'InitLedger' function.
        initLedger();

        // Return all the current assets on the ledger.
        getAllAssets();

        // Create a new asset on the ledger.
        createAsset();

        // Update an existing asset asynchronously.
        transferAssetAsync();

        // Get the asset details by assetID.
        readAssetById();

        // Update an asset which does not exist.
        updateNonExistentAsset();
    }


    private void initLedger() throws EndorseException, SubmitException, CommitStatusException, CommitException{
        System.out.println("\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger");
//        contract.submitTransaction("InitLedger");
        contract.submitTransaction("InitLedger");
        System.out.println("*** Transaction committed successfully");
    }

//评估交易以及查询
//    *

    private void getAllAssets() throws GatewayException{
        System.out.println("\n--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger");
        byte[] result = contract.evaluateTransaction("GetAllAssets");

        System.out.println("*** Result: " + prettyJson(result));
    }

//格式转化
//    *

    private String prettyJson(final byte[] json) {
        return prettyJson(new String(json, StandardCharsets.UTF_8));
    }

    private String prettyJson(final String json) {
        JsonElement parsedJson = JsonParser.parseString(json);
        return gson.toJson(parsedJson);
    }

//同步提交 注意这边同步肯定会影响用户体验 跑完后记得优化
//    *

    private void createAsset() throws EndorseException, SubmitException, CommitStatusException, CommitException{
        System.out.println("\n--> Submit Transaction: CreateAsset, creates new asset with ID, Color, Size, Owner and AppraisedValue arguments");
//提交信息
//        *

        contract.submitTransaction("CreateAsset", assetId, "yellow", "5", "Tom", "1300");
        System.out.println("*** Transaction committed successfully");
    }

//这边是异步指令 那么就是说访问账本可以使用这里
//    *

    private void transferAssetAsync() throws EndorseException, SubmitException, CommitStatusException{
        System.out.println("\n--> Async Submit Transaction: TransferAsset, updates existing asset owner");
        SubmittedTransaction commit = contract.newProposal("TransferAsset")
                .addArguments(assetId, "Saptha")
                .build()
//背书

                .endorse()
//异步提交
//                 *

                .submitAsync();

        byte[] result = commit.getResult();
        String oldOwner = new String(result, StandardCharsets.UTF_8);

        System.out.println("*** Successfully submitted transaction to transfer ownership from " + oldOwner + " to Saptha");
        System.out.println("*** Waiting for transaction commit");

        Status status = commit.getStatus();
        if (!status.isSuccessful()){
            throw new RuntimeException("Transaction " + status.getTransactionId() +
                    " failed to commit with status code " + status.getCode());
        }
        System.out.println("*** Transaction committed successfully");
    }
//读取资产id
//    *

    private void readAssetById() throws GatewayException{
        System.out.println("\n--> Evaluate Transaction: ReadAsset, function returns asset attributes");
        byte[] evaluateResult = contract.evaluateTransaction("ReadAsset", assetId);
        System.out.println("*** Result:" + prettyJson(evaluateResult));
    }

//*
//     * submitTransaction() will throw an error containing details of any error
//     * responses from the smart contract.
//

    private void updateNonExistentAsset(){
        try {
            System.out.println("\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error");
            contract.submitTransaction("UpdateAsset", "asset70", "blue", "5", "Tomoko", "300");
            System.out.println("******** FAILED to return an error");
        }catch (EndorseException | SubmitException | CommitStatusException e){
            System.out.println("*** Successfully caught the error: ");
            e.printStackTrace(System.out);
            System.out.println("Transaction ID: " + e.getTransactionId());

            List<ErrorDetail> details = e.getDetails();
            if (!details.isEmpty()){
                System.out.println("详细信息错误");
                for (ErrorDetail detail : details){
                    System.out.println("- address: " + detail.getAddress() + ", mspId: " + detail.getMspId()
                            + ", message: " + detail.getMessage());
                }
            }
        }catch (CommitException e){
            System.out.println("*** Successfully caught the error: " + e);
            e.printStackTrace(System.out);
            System.out.println("Transaction ID: " + e.getTransactionId());
            System.out.println("Status code: " + e.getCode());
        }
    }

}
